/*
 * Projekt: GMU 2013 - Canny hranovy detektor OpenCL/CUDA
 * Autori: Pavel Nevrala - xnevra00 (xnevra00@stud.fit.vutbr.cz)
 *         Martin Jurak - xjurak02 (xjurak02@stud.fit.vutbr.cz)
 */
#include "CannyDetectorCL.h"
#include "GaussianFilterCL.h"
#include "SobelFilterCL.h"
#include "NonMaxSupCL.h"
#include "DoubleThresholdingCL.h"

CannyDetectorCL::CannyDetectorCL(Device device) //definice konstruktoru
{
	//OpenCL
	cl_uint mclDevicesCount,mclPlatformsCount, intVal;	//pocet zarizeni a platform
    cl_platform_id* mclPlatforms;						//id platformy
	cl_device_id *mclDevices;							//id zarizeni
	cl_device_type deviceType;							//typ zarizeni
	char name[1024];									//jmeno
    
	//ziskani platformy	
	clGetPlatformIDs(0, NULL, &mclPlatformsCount);	
	mclPlatforms = (cl_platform_id*)malloc(sizeof(cl_platform_id)*mclPlatformsCount);
	clGetPlatformIDs(mclPlatformsCount, mclPlatforms, NULL);

	int isGPU = 0;
	for(int i =0; i < (int)mclPlatformsCount; i++){
		clGetDeviceIDs(mclPlatforms[i], CL_DEVICE_TYPE_ALL, 0, NULL, &mclDevicesCount); 	
		mclDevices = (cl_device_id*)malloc(sizeof(cl_device_id)*mclDevicesCount);
		clGetDeviceIDs(mclPlatforms[i], CL_DEVICE_TYPE_ALL, mclDevicesCount, mclDevices, NULL);	
		
		//ziskani zarizeni
		clGetDeviceInfo(mclDevices[0], CL_DEVICE_TYPE, sizeof(cl_device_type), &deviceType, NULL);
		clGetDeviceInfo(mclDevices[0], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(cl_int), &intVal, NULL);		

		//nastavit zarizeni id,maximalni WorkGroup size podle typu
		if((deviceType == CL_DEVICE_TYPE_CPU) && (device == CPU)){
			deviceId = mclDevices[0];
			maxWorkGroup = intVal;		
		}
		else if((deviceType == CL_DEVICE_TYPE_GPU) && (device == GPU)){
			deviceId = mclDevices[0];
			maxWorkGroup = intVal;		
			isGPU = 1;
		}
	}	

	if(isGPU == 0){
		cerr << "ERROR: Nenalezena graficka karta!"<<endl;
		throw -1;
	}

	clGetDeviceInfo(deviceId, CL_DEVICE_NAME, 1024, name, NULL);

	cout << "\nCL_DEVICE_NAME: " << name << endl;
	cout << "CL_DEVICE_MAX_WORKGROUP: " << maxWorkGroup << endl;
	
	cl_int clErr;
	//vytvoreni contextu
	context = clCreateContext(NULL, 1, &deviceId, NULL, NULL, &clErr); 
	errorController.CheckOpenCLError(clErr, "clCreateContext.");
	

    //vytvoreni command queue
    commandQueue = clCreateCommandQueue(context, deviceId, CL_QUEUE_PROFILING_ENABLE,&clErr);// CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE| CL_QUEUE_PROFILING_ENABLE
	errorController.CheckOpenCLError(clErr, "clCreateCommandQueue.");	

	char *cSourceCL = LoadProgSource("../canny.cl");	
	//vytvoreni programu z nacitaneho zdrojaku
    program = clCreateProgramWithSource(context, 1, (const char **)&cSourceCL, NULL, &clErr);
	errorController.CheckOpenCLError(clErr, "clCreateProgramWithSource.");	
	free(cSourceCL);

	//kompilace programu	
	clErr = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);	

	//build log
    char *buildLog = NULL;
    size_t buildLogSize = 0;
	cl_int logStatus;
    logStatus = clGetProgramBuildInfo( program, 
										deviceId, 
										CL_PROGRAM_BUILD_LOG, 
										buildLogSize, 
										buildLog, 
										&buildLogSize);
	
	errorController.CheckOpenCLError(logStatus, "clGetProgramBuildInfo.");
	

    buildLog = new char[buildLogSize];
    if(buildLog == NULL)
    {
        cerr << "Failed to allocate host memory. (buildLog)" << endl;
        throw -1;;
    }
    memset(buildLog, 0, buildLogSize);

    logStatus = clGetProgramBuildInfo (program, 
										deviceId, 
										CL_PROGRAM_BUILD_LOG, 
										buildLogSize, 
										buildLog, 
										NULL);
	errorController.CheckOpenCLError(logStatus, "clGetProgramBuildInfo.");	

    printf(" \n\t\t\tBUILD LOG\n");
    printf(" ************************************************\n");
    printf("%s", buildLog);
    printf(" ************************************************\n");
    delete[](buildLog);
	
	errorController.CheckOpenCLError( clErr, "clBuildProgram" );	
}

// Uvolneni zdroju
CannyDetectorCL::~CannyDetectorCL(void) //definice destruktoru
{
	cl_int clErr;	
	clErr = clReleaseProgram(program);
	errorController.CheckOpenCLError(clErr, "clReleaseProgram.");

    clErr = clReleaseCommandQueue(commandQueue);
	errorController.CheckOpenCLError(clErr, "clReleaseCommandQueue.");

    clErr = clReleaseContext(context);
	errorController.CheckOpenCLError(clErr, "clReleaseContext.");	
}

/*
 * Detekce hran
 * @inImage - vstupni obraz
 * @outImage - vystupni obraz
 * @gaussMask - Gaussova maska
 * @threshold - prah 
 */
void CannyDetectorCL::detectEdges(ImageCL &inImage, ImageCL &outImage, GaussianMask &gaussMask, int threshold1, int threshold2)
{
	GaussianFilterCL gaussFilter(maxWorkGroup ,&gaussMask, &context, &commandQueue, &program);
	SobelFilterCL sobelFilter( maxWorkGroup, &context, &commandQueue, &program);	
	double gaussT, sobelT, nonMaxSupT, doubleThrT;

	// Gaussuv filtr
	gaussT = gaussFilter.run(inImage.getImageCL(), inImage.width(), inImage.height());
	//outImage.loadImage(gaussFilter.getOutImage(), inImage.width(), inImage.height());

	// Sobeluv operator
	sobelT = sobelFilter.run(gaussFilter.getOutImage(), inImage.width(), inImage.height());
	//outImage.loadImage(sobelFilter.getOutImage(), inImage.width(), inImage.height());

	// Hledani lokalnich maxim
	NonMaxSupCL nonMaxSup(maxWorkGroup, sobelFilter.getEdgesDirection(), &context, &commandQueue, &program);
	nonMaxSupT = nonMaxSup.run(sobelFilter.getOutImage(), inImage.width(), inImage.height());
	//outImage.loadImage(nonMaxSup.getOutImage(), inImage.width(), inImage.height());

	// Double thresholding s hysterezi
	DoubleThresholdingCL doubleThresholding(maxWorkGroup, &context, &commandQueue, &program, sobelFilter.getEdgesDirection());
	doubleThrT = doubleThresholding.run(nonMaxSup.getOutImage(), inImage.width(), inImage.height(),threshold1,threshold2);
	outImage.loadImage(doubleThresholding.getOutImage(), inImage.width(), inImage.height());
	
	// Vypis jednotlivych casu fazi Cannyho hranoveho detektoru v OpenCL
	std::cout << "\n*****OPENCL CANNY DETECTOR*****" << std::endl;
	std::cout << "OpenCL Gauss filter:         " << gaussT << "ms"<< std::endl;
	std::cout << "OpenCL Sobel:                " << sobelT << "ms"<< std::endl;
	std::cout << "OpenCL nonMaxSup:            " << nonMaxSupT << "ms"<< std::endl;
	std::cout << "OpenCL Doublethrsholding:    " << doubleThrT << "ms"<< std::endl;
	std::cout << "Canny detector OpenCL:       " << gaussT+sobelT+nonMaxSupT+doubleThrT << "ms"<< std::endl;	
}

////////////////////////////
// Prevzato ze cviceni GMU
////////////////////////////
/*
 * Nacteni zdrojoveho programu pro kernely
 * @cFilename - cesta k souboru	 
 */
char* CannyDetectorCL::LoadProgSource(const char* cFilename)
{
    FILE* pFileStream = NULL;
    size_t szSourceLength;

    // otevri OpenCL zdrojak
#ifdef _WIN32   // Windows verzia
    if(fopen_s(&pFileStream, cFilename, "rb") != 0)
    {
        return NULL;
    }
#else           // Linux verzia
    pFileStream = fopen(cFilename, "rb");
    if(pFileStream == 0)
    {
        return NULL;
    }
#endif

    // ziskame velikost suboru
    fseek(pFileStream, 0, SEEK_END);
    szSourceLength = ftell(pFileStream);
    fseek(pFileStream, 0, SEEK_SET);

    // alokace
    char* cSourceString = (char *)malloc(szSourceLength + 1);
    if (fread(cSourceString, szSourceLength, 1, pFileStream) != 1)
    {
        fclose(pFileStream);
        free(cSourceString);
        return 0;
    }
    
    fclose(pFileStream);
    cSourceString[szSourceLength] = '\0';

    return cSourceString;
}